package com.googlecode.roids.os;

import android.os.Handler;
import android.os.Message;

/**
 * Parentable task object for doing long running operations.
 *
 * @param <Arg>
 * @param <Result>
 * @param <Owner>
 */
public abstract class Task<Arg, Result, Progress, Owner> {

	private static final int MSG_CALLBACK = 1;
	
	private Owner owner;
	private Object ownerLock;
	private Thread thread;
	private Handler handler;
	private boolean wasCanceled;
	private boolean isFinished;
	private boolean isRunning;
	
	/**
	 * Creates the task and assigns it the
	 * given parent.
	 * @param owner the parent
	 */
	public Task() {
		this.ownerLock		= new Object();
		this.wasCanceled	= false;
		this.isFinished		= false;
		this.isRunning		= false;
	}
	
	/**
	 * Creates the task and assigns it the
	 * given parent.
	 * @param parent the parent
	 */
	public Task(Owner parent) {
		this();
		setOwner(parent);
	}
	
	/**
	 * Checks to see if the thread is running.
	 * @return
	 */
	public final boolean isRunning() {
		return isRunning;
	}
	
	/**
	 * Checks to see if the task is finished.
	 * @return
	 */
	public final boolean isFinished() {
		return isFinished;
	}
	
	/**
	 * Cancels the task if it is currently
	 * running.
	 */
	public final void cancel() {
		if (!this.isRunning) {
			throw new IllegalStateException(
				"Task is not running");
		}
		this.wasCanceled = true;
		this.thread.interrupt();
		synchronized(ownerLock) {
			ownerLock.notifyAll();
		}
	}
	
	/**
	 * Sets the tasks parent.
	 * @param parent the parent
	 */
	public void setOwner(Owner parent) {
		synchronized(ownerLock) {
			this.owner = parent;
			ownerLock.notifyAll();
		}
	}
	
	/**
	 * Retreives the tasks parent.
	 * @return the parent
	 */
	public Owner getOwner() {
		synchronized(ownerLock) {
			while (!wasCanceled && this.owner==null) {
				try {
					ownerLock.wait();
				} catch(InterruptedException ie) { }
			}
			return this.owner;
		}
	}
	
	/**
	 * Simple execute callback.
	 * @param args the arguments
	 * @return the result
	 * @throws InterruptedException if it was interrupted/canceled
	 */
	private final Result doExecute(Arg... args)
		throws InterruptedException,
		Exception {
		return onExecute(args);
	}
	
	/**
	 * Sends progress to do onProgress callback.
	 * @param progress the progress to send
	 */
	public final void sendProgress(final Progress... progress) {
		sendCallback(new Runnable() {
			public void run() {
				Task.this.onProgress(progress);
			}
		});
	}
	
	/**
	 * Sends a callback to the Handler.
	 * @param runnable the callback
	 */
	private void sendCallback(Runnable runnable) {
		handler.obtainMessage(MSG_CALLBACK, runnable).sendToTarget();
	}
	
	/**
	 * Executes the task with the given arguments.
	 * @param args the arguments
	 * @return the task itself
	 */
	public final Task<Arg, Result, Progress, Owner> execute(final Arg... args) {
		
		// can't run twice
		if (isRunning() || isFinished()) {
			throw new IllegalStateException(
				"The task is running or has finished");
		}
		
		// create handler
		this.handler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				switch(msg.what) {
				case MSG_CALLBACK:
					((Runnable)msg.obj).run();
				}
			}
		};
		
		// create the thread
		thread = new Thread(new Runnable() {
			public void run() {
				try {
					
					// onExecute
					Task.this.isRunning = true;
					final Result result = doExecute(args);
					
					// onAfterExecute
					sendCallback(new Runnable() {
						public void run() {
							Task.this.onAfterExecute(result);
						}
					});
					
				// onCanceled
				} catch(InterruptedException ie) {
					sendCallback(new Runnable() {
						public void run() {
							Task.this.onCanceled();
						}
					});
					
				// onException
				} catch(final Throwable t) {
					sendCallback(new Runnable() {
						public void run() {
							Task.this.onException(t);
						}
					});
					
				// onFinish
				} finally {
					Task.this.isRunning = false;
					Task.this.isFinished = true;
					sendCallback(new Runnable() {
						public void run() {
							Task.this.onFinish();
						}
					});
				}
			}
		}, "Task-thread-"+hashCode());

		// onBeforeExecute
		Task.this.onBeforeExecute(args);
		
		// start the thread
		thread.start();
		
		// we're good to go
		return this;
	}
	
	/**
	 * Callback that's executed on a new thread.
	 * @param args the arguments to the task
	 * @return the result of the task
	 */
	protected abstract Result onExecute(Arg... args)
		throws Exception;
	
	/**
	 * Callback that's executed on the thread that
	 * called {@link #execute(Object...)}.
	 */
	protected void onProgress(Progress... progress) {
		// no-op
	}
	
	/**
	 * Callback that's executed on the thread that
	 * called {@link #execute(Object...)}.
	 */
	protected void onBeforeExecute(Arg... args) {
		// no-op
	}
	
	/**
	 * Callback that's executed on the thread that
	 * called {@link #execute(Object...)}.
	 */
	protected void onAfterExecute(Result result) {
		// no-op
	}
	
	/**
	 * Callback that's executed on the thread that
	 * called {@link #execute(Object...)}.
	 */
	protected void onCanceled() {
		// no-op
	}
	
	/**
	 * Callback that's executed on the thread that
	 * called {@link #execute(Object...)}.
	 */
	protected void onException(Throwable exception) {
		// no-op
	}
	
	/**
	 * Callback that's executed on the thread that
	 * called {@link #execute(Object...)}.
	 */
	protected void onFinish() {
		// no-op
	}
	
}
